#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/semaphore.h>
#include <net/genetlink.h>
#include <linux/netlink.h>
#include "vtl.h"

#define _DEBUG

//Dumps
#ifdef _DEBUG
#define printd(x...) printk(KERN_DEBUG "DEBUG: " x)
#endif


/* Prototypes */
static int send_msg(void *buff, unsigned int len, int attr);
static inline int send_buffer_msg(void *buff, unsigned int len);
static inline int send_param_msg(uint32_t param, int attr);
static int device_open(struct inode *inode, struct file *file);
static int device_release(struct inode *inode, struct file *file);
static ssize_t device_read(struct file *file, char __user * buffer, size_t length,
        loff_t * offset);
static ssize_t device_write(struct file *file, const char __user * buffer, size_t length, loff_t * offset);
int device_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param);
int init_module(void);
void cleanup_module(void);
int VTL_read_buffer(struct sk_buff *skb, struct genl_info *info);
int VTL_read_return_val(struct sk_buff *skb_2, struct genl_info *info);


/* Global Variables */
static int Device_Open = 0;
static char mydata[MAX_PACKET_SIZE];
static int myretval;
static struct semaphore sem_buffer;
static struct semaphore sem_retval;
static struct semaphore sem_one_op;
static struct semaphore sem_mydata;
static struct semaphore sem_myretval;

static int user_pid = 0;

/* attributes (variables): the index in this enum is used as a reference for the type,
 *             userspace application has to indicate the corresponding type
 *             the policy is used for security considerations 
 */
enum {
    VTL_A_UNSPEC,
    VTL_A_BUFFER_SIZE,
    VTL_A_BUFFER,
    VTL_A_CREATE_TAPE,
    VTL_A_REWIND_TAPE,
    VTL_A_REPLACE_TAPE,
    VTL_A_DELETE_TAPE,
    __VTL_A_MAX,
};
#define VTL_A_MAX (__VTL_A_MAX - 1)

/* attribute policy: defines which attribute has which type (e.g int, char * etc)
 * possible values defined in net/netlink.h 
 */
static struct nla_policy VTL_genl_policy[VTL_A_MAX + 1] = {
    [VTL_A_BUFFER_SIZE] =
    { .type = NLA_U32},
    [VTL_A_BUFFER] =
    { .type = NLA_BINARY},
    [VTL_A_CREATE_TAPE] =
    { .type = NLA_U32},
    [VTL_A_REWIND_TAPE] =
    { .type = NLA_U32},
    [VTL_A_REPLACE_TAPE] =
    { .type = NLA_U32},
    [VTL_A_DELETE_TAPE] =
    { .type = NLA_U32},
};

#define VERSION_NR 1
/* family definition */
static struct genl_family VTL_gnl_family = {
    .id = GENL_ID_GENERATE, //genetlink should generate an id
    .hdrsize = 0,
    .name = FAMILY_NAME, //the name of this family, used by userspace application
    .version = VERSION_NR, //version number
    .maxattr = VTL_A_MAX,
};

/* commands: enumeration of all commands (functions), 
 * used by userspace application to identify command to be executed
 */
enum {
    VTL_C_UNSPEC,
    VTL_C_RET_VAL, //callback for getting just a return value
    VTL_C_RET_BUFFER, //callback for getting a buffer (like in read())
    __VTL_C_MAX,
};
#define VTL_C_MAX (__VTL_C_MAX - 1)

/**
 * This is the CallBack for VTL_C_RET_BUFFER - will be called when daemon returns
 * a buffer (after performin read()).
 * params are the responsibility of genl.
 **/
int VTL_read_buffer(struct sk_buff *skb, struct genl_info *info) {
    struct nlattr *na;
    char * tmpp = NULL;
    int i = 0;
    printd("in read_buffer()\n");

    if (info == NULL)
        goto out;

    /*for each attribute there is an index in info->attrs which points to a nlattr structure
     in this structure the data is given */
    na = info->attrs[VTL_A_BUFFER];
    if (na) {
        if (0 != user_pid) down(&sem_mydata);

        tmpp = (char *)nla_data(na);
        myretval = (int) nla_len(na);
        //ichs
        
        for (i = 0; i < myretval; i++)
            mydata[i] = tmpp[i];
        if (mydata == NULL)
            printd("error while receiving data\n");
        else
            printd("received data %s\n", mydata);
    } else
        printd("no info->attrs %i\n", VTL_A_BUFFER);

    // Don't unlock the semaphore if it's the first recv ever (if pid is still 0)
    if (0 != user_pid) {
        up(&sem_buffer);
    }

    user_pid = info->snd_pid;
    return 0;

out:
    printd("an error occured in VTL_read_buffer\n");
    return 0;
}

/**
 * This is the CallBack for VTL_C_RET_VAL - will be called when daemon returns
 * a return value (after write, replace.. etc)
 * params are the responsibility of genl.
 **/
int VTL_read_return_val(struct sk_buff *skb_2, struct genl_info *info) {
    struct nlattr *na;
    printd("in read_return_val()\n");
    if (info == NULL)
        goto out;

    /*for each attribute there is an index in info->attrs which points to a nlattr structure
     in this structure the data is given */
    na = info->attrs[VTL_A_BUFFER_SIZE]; //VTL_A_BUFFER_SIZE will use us also for return values.
    if (na) {
        if (nla_data(na) == 0)
            printd("error while receiving data\n");
        else
            myretval = *((int*) nla_data(na));
        printd("received data %u\n", myretval);
    } else
        printd("no info->attrs %i\n", VTL_A_BUFFER_SIZE);

    // Unlock semaphore only if it's not the first time recv called (if pid is still 0)
    if (0 != user_pid)
        up(&sem_retval);

    user_pid = info->snd_pid;

    return 0;
out:
    printd("an error occured in VTL_read_retval()\n");

    return 0;
}

// Structs for setting the callbacks
struct genl_ops VTL_gnl_ops_read_buffer = {
    .cmd = VTL_C_RET_BUFFER,
    .flags = 0,
    .policy = VTL_genl_policy,
    .doit = VTL_read_buffer,
    .dumpit = NULL,
};

struct genl_ops VTL_gnl_ops_read_return_val = {
    .cmd = VTL_C_RET_VAL,
    .flags = 0,
    .policy = VTL_genl_policy,
    .doit = VTL_read_return_val,
    .dumpit = NULL,
};


//Sends a message using the VTL_A_BUFFER attribute to our family
//For more info see send_msg.
//Return send_msg returns.

static inline int send_buffer_msg(void *buff, unsigned int len) {
    printd("Sending message: buffer of length %d", len);
    return send_msg(buff, len, VTL_A_BUFFER);
}

//Sends a message using the given attr attribute to our family
//For more info see send_msg.
//Return send_msg returns.

static inline int send_param_msg(uint32_t param, int attr) {
    printd("Sending message: attr %d, param %d\n", attr, param);
    return send_msg(0, param, attr);
}

//Sends a message using the attr attribute to our family.
//The message will be NLM_F_MULTI - termination can be done by hand.
//Return 0 on success.
//Returns -ENOMEM if not enough mem.
//Returns -EMSGSIZE if not enough room in the message to store the buffer.
//(This might happen because of nla_put).

static int send_msg(void *buff, unsigned int len, int attr) {
    struct sk_buff *skb;
    void *msg_head;
    int rc = -2;

    skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    if (skb == NULL) {
        printd("send_msg(): failed to allocate msg\n");
        rc = -ENOMEM;
        goto msg_err;
    }

    /* Create the message headers */
    msg_head = genlmsg_put(skb, 0, 0, &VTL_gnl_family,
            NLM_F_MULTI, //Always send a multi message
            0); //This is probably meaningless.
    //NLM_F_MULTI can also be used if you need more than one message.
    //For instance you can use a loop to make sure you send as much
    //data as needed.
    //Then you do NLM_F_MULTI and NLM_F_DONE at the end.

    if (msg_head == NULL) {
        printd("send_msg(): failed to add msg header\n");
        rc = -ENOMEM;
        goto msg_err;
    }

    if (buff != 0)
        rc = nla_put(skb, attr, len, buff);
    else //if buff==0 then this is a param send
        rc = nla_put_u32(skb, attr, len);
    if (rc != 0) {
        printd("send_msg(): failed to put buff/param to msg\n");
        goto msg_err;
    }

    /* Finalize the message (Cannot fail).*/
    genlmsg_end(skb, msg_head);

    rc = genlmsg_unicast(skb, user_pid);
    goto end;

msg_err:
    printd("send_msg() fail\n");
    if (skb == NULL)
        goto end;

    if (msg_head != NULL)
        genlmsg_cancel(skb, msg_head);

    nlmsg_free(skb);
end:
    return rc;
}

/*
 * This is called whenever a process attempts to open the device file
 */
static int device_open(struct inode *inode, struct file *file) {
    printd("Opening device\n");
    // We don't want to talk to two processes at the same time
    if (Device_Open)
        return -EBUSY;
    Device_Open++;

    init_MUTEX_LOCKED(&sem_buffer);
    init_MUTEX_LOCKED(&sem_retval);
    init_MUTEX(&sem_one_op);
    init_MUTEX(&sem_mydata);
    init_MUTEX(&sem_myretval);

    try_module_get(THIS_MODULE);
    return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file) {
    // We're now ready for our next caller
    printd("Releasing device\n");
    Device_Open--;
    module_put(THIS_MODULE);
    return SUCCESS;
}

/*
 * This function is called whenever a process which has already opened the
 * device file attempts to read from it.
 */
static ssize_t device_read(struct file *file, /* see include/linux/fs.h   */
        char __user * buffer, /* buffer to be
                                                         * filled with data */
        size_t length, /* length of the buffer     */
        loff_t * offset) {
    /*
     * Number of bytes actually written to the buffer
     */
    int bytes_read = 0;
    int bytes_to_read = 0;
    int i = 0;
    printd("Received a read request for %d bytes.\n", length);
    down(&sem_one_op);
    down(&sem_myretval);

    do {
        if (length > MAX_PACKET_SIZE)
            bytes_to_read = MAX_PACKET_SIZE;
        else
            bytes_to_read = length;

        length -= bytes_to_read;

        if (0 != send_param_msg(bytes_to_read, VTL_A_BUFFER_SIZE)) {
            printd("device_read(): send_param_msg() failed\n");
            up(&sem_one_op);
            up(&sem_myretval);
            return -1;
        }
            
        down(&sem_buffer);
        bytes_to_read = myretval;
        i=0;
        while (bytes_to_read > 0) {
            /*
             * Because the buffer is in the user data segment,
             * not the kernel data segment, assignment wouldn't
             * work. Instead, we have to use put_user which
             * copies data from the kernel data segment to the
             * user data segment.
             */
            put_user(mydata[i++], buffer++);
            bytes_to_read--;
            bytes_read++;
        }
        up(&sem_mydata);

    } while ((length > 0) && (myretval > 0));


    up(&sem_myretval);
    printd("device_read(): returning %d bytes\n", bytes_read);

    up(&sem_one_op);
    return bytes_read;
}

/*
 * This function is called when somebody tries to
 * write into our device file.
 */
static ssize_t
device_write(struct file *file,
        const char __user * buffer, size_t length, loff_t * offset) {
    int i = 0, bytes_to_write = 0, bytes_written = 0;

    down(&sem_one_op);
    down(&sem_myretval);

    printd("device_write(): received a write request for %d bytes\n", length);

    do {
        if (length > MAX_PACKET_SIZE)
            bytes_to_write = MAX_PACKET_SIZE;
        else
            bytes_to_write = length;
        printd("bytes_to_write: %d\n",bytes_to_write);

        length -= bytes_to_write;

        copy_from_user(mydata, (buffer + bytes_written), bytes_to_write);
        printd("copied data\n");

        if (0 != send_buffer_msg(mydata, bytes_to_write)) {
            printd("device_write(): send_buffer_msg() failed\n");
            up(&sem_myretval);
            up(&sem_one_op);
            return -1;
        }

        down(&sem_retval);
        i = myretval;
        bytes_written += i;
    } while ((i > 0) && (length > 0));

    up(&sem_myretval);
    up(&sem_one_op);
    return bytes_written;
}

/*
 * This function is called whenever a process tries to do an ioctl on our
 * device file. We get two extra parameters (additional to the inode and file
 * structures, which all device functions get): the number of the ioctl called
 * and the parameter given to the ioctl function.
 *
 * If the ioctl is write or read/write (meaning output is returned to the
 * calling process), the ioctl call returns the output of this function.
 *
 */
int device_ioctl(struct inode *inode, /* see include/linux/fs.h */
        struct file *file, /* ditto */
        unsigned int ioctl_num, /* number and param for ioctl */
        unsigned long ioctl_param) {
    uint32_t ret=0;
    uint32_t param=0;
    down(&sem_one_op);
    /*
     * Switch according to the ioctl called
     */
    switch (ioctl_num) {
        case IOCTL_CREATE_TAPE:
            printd("device_ioctl(): CREATE_TAPE\n");
            down(&sem_myretval);
            send_param_msg(0, VTL_A_CREATE_TAPE);
            down(&sem_retval);
            if (myretval == -1) {
                up(&sem_myretval);
                up(&sem_one_op);
                return IOCTL_FAILURE;
            }
            ret = myretval;
            up(&sem_myretval);
            up(&sem_one_op);
            return copy_to_user((uint32_t*) ioctl_param, &ret, sizeof (uint32_t));
            

        case IOCTL_DELETE_TAPE:
            copy_from_user(&param, (uint32_t*)ioctl_param, sizeof(uint32_t));
            printd("device_ioctl(): DELETE_TAPE %d\n", param);
            send_param_msg(param, VTL_A_DELETE_TAPE);
            down(&sem_retval);
            up(&sem_one_op);
            return myretval;
            
        case IOCTL_REPLACE_TAPE:
            copy_from_user(&param, (uint32_t*)ioctl_param, sizeof(uint32_t));
            printd("device_ioctl(): REPLACE_TAPE %d\n", param);
            send_param_msg(param, VTL_A_REPLACE_TAPE);
            down(&sem_retval);
            up(&sem_one_op);
            return myretval;
            
        case IOCTL_REWIND_CURRENT_TAPE:
            printd("device_ioctl(): REWIND_TAPE\n");
            send_param_msg(0, VTL_A_REWIND_TAPE);
            down(&sem_retval);
            up(&sem_one_op);
            return myretval;
    }

    up(&sem_one_op);
    return SUCCESS;
}

/* Module Declarations */

/*
 * This structure will hold the functions to be called
 * when a process does something to the device we
 * created. Since a pointer to this structure is kept in
 * the devices table, it can't be local to
 * init_module. NULL is for unimplemented functions.
 */
struct file_operations Fops = {
    .read = device_read,
    .write = device_write,
    .ioctl = device_ioctl,
    .open = device_open,
    .release = device_release, /* a.k.a. close */
};

/*
 * This function is called when the module is loaded
 */
int init_module(void) {
    int ret_val;
    printd("Started module.\n");
    /*
     * Register the character device (atleast try)
     */
    ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
    /*
     * Negative values signify an error
     */
    if (ret_val < 0) {
        printd("%s failed with %d\n",
                "Sorry, registering the character device ", ret_val);
        return ret_val;
    }


    /*register new family*/
    ret_val = genl_register_family(&VTL_gnl_family);
    if (ret_val != 0) {
        printd("%s failed with %d\n",
                "Generic Netlink family registration ", ret_val);
        return ret_val;
    }
    /*register functions (commands) of the new family*/
    ret_val = genl_register_ops(&VTL_gnl_family, &VTL_gnl_ops_read_buffer);
    if (ret_val != 0) {
        
        genl_unregister_family(&VTL_gnl_family);
        return ret_val;
    }

    ret_val = genl_register_ops(&VTL_gnl_family, &VTL_gnl_ops_read_return_val);
    if (ret_val != 0) {
        genl_unregister_family(&VTL_gnl_family);
        return ret_val;
    }


    return SUCCESS;
}

/*
 * This function is called when the module is unloaded
 */
void cleanup_module(void) {
    int ret;

    /*
     * Unregister the device
     */
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

    /*unregister the functions*/
    ret = genl_unregister_ops(&VTL_gnl_family, &VTL_gnl_ops_read_buffer);
    if (ret != 0) {

        return;
    }

    ret = genl_unregister_ops(&VTL_gnl_family, &VTL_gnl_ops_read_return_val);
    if (ret != 0) {
        return;
    }

    /*unregister the family*/
    ret = genl_unregister_family(&VTL_gnl_family);
    if (ret != 0) {
        printd("unregister family %i\n", ret);
    }


}

